package com.apidoc.utis;

import com.alibaba.fastjson.JSONObject;
import com.apidoc.common.Const;
import com.apidoc.bean.ParamItem;
import com.apidoc.bean.Params;
import com.apidoc.common.VO;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.io.InputStreamSource;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

/**
 * Class反射工具
 */
public class ClassUtil {
    /**
     * 封装基本类型和参数类型的对应关心
     */
    private static final Map<Class, String> typeMap = new HashMap<>();

    //初始化
    static {
        typeMap.put(byte.class, Const.number);
        typeMap.put(short.class, Const.number);
        typeMap.put(int.class, Const.number);
        typeMap.put(long.class, Const.number);
        typeMap.put(float.class, Const.number);
        typeMap.put(double.class, Const.number);
        typeMap.put(char.class, Const.string);
        typeMap.put(boolean.class, Const.booleann);
    }

    /**
     * 根据类全名获得类对象
     *
     * @param className 类全名
     * @return Class
     */
    public static Class getClassByName(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获得方法的参数列表
     *
     * @param method     方法对象
     * @param methodUUID 唯一方法名
     * @return Params
     */
    public static Params getParams(Method method, String methodUUID) {
        //1.封装响应数据
        Params params = new Params();
        //2.设置请求或响应类型
        params.setType(getType(method));
        List<ParamItem> paramItems = getParamItems(method, methodUUID);
        List<ParamItem> paramItemList = list2Tree(paramItems);

        //3.设置参数列表
        params.setParams(paramItemList);
        return params;
    }

    /**
     * 将list数据转换为tree结构数据
     *
     * @param params
     * @return
     */
    private static List<ParamItem> list2Tree(List<ParamItem> params) {
        if (null == params || params.size() == 0) {
            return null;
        }
        List<ParamItem> trees = new ArrayList<>();
        for (ParamItem treeNode : params) {
            if ("0".equals(treeNode.getPid())) {
                trees.add(treeNode);
            }
            for (ParamItem it : params) {
                if (it.getPid().equals(treeNode.getName())) {
                    if (treeNode.getList() == null) {
                        treeNode.setList(new ArrayList<ParamItem>());
                    }
                    treeNode.getList().add(it);
                }
            }
        }
        return trees;
    }

    /**
     * 获取参数列表
     *
     * @param method     方法
     * @param methodUUID 唯一方法名
     * @return List<ParamItem>
     */
    private static List<ParamItem> getParamItems(Method method, String methodUUID) {
        List<ParamItem> list = new ArrayList<>();
        //1.得到参数名  为了得到参数名
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        //2.得到参数类型 为了方便判断类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        //3.得到参数的通用类型
        Type[] genericParameterTypes = method.getGenericParameterTypes();

        for (int i = 0; i < parameterTypes.length; i++) {
            //请求参数的类型 只能是 字符串string 数字number 自定义对象 数组（普通数组，对象数组） list map 文件
            isType(parameterTypes[i], paramNames[i], null, list, genericParameterTypes[i], methodUUID, null, true, true, false);
        }
        return list;
    }

    /**
     * 判断参数的类型
     * 请求参数的类型 只能是 字符串string 数字number 自定义对象 数组（普通数组，对象数组） 泛型（list map） 文件 boolean 日期Data
     *  @param tclass     参数的Class
     * @param paramName  参数名
     * @param pid        父id 父对象名称
     * @param list       参数项列表
     * @param genType    参数的通用类型
     * @param methodUUID 唯一方法名
     * @param po         参数列表持久化之后的数据结构
     * @param isFirst    是否是第一个对象
     * @param isSelf     是否是对象的 自嵌套
     * @param isReturn
     */
    private static void isType(Class tclass, String paramName, String pid, List<ParamItem> list, Type genType, String methodUUID, JSONObject po, boolean isFirst, boolean isSelf, boolean isReturn) {
        String path = Const.actionParams + methodUUID + ".json";
        if(isReturn){
            path = Const.actionParams+methodUUID+"-return.json";
        }
        //读取硬盘数据
        if (po == null) {
            po = new JSONObject();
            if (StringUtil.isNotEmpty(methodUUID)) {
                po = JsonUtil.readerObject(path);
            }
        }

        if (StringUtil.isEmpty(paramName)) {
            paramName = tclass.getSimpleName();
        }

        String key = paramName + "-" + pid;//父参数名+子参数名 来唯一确定一个参数
        ParamItem item = getParamItem(tclass, paramName, po, key);

        ////1.设置父id 名称
        if (StringUtil.isNotEmpty(pid)) {
            item.setPid(pid);
        }

        ////2.设置参数类型
        ////2.1. 是数组 或者多维数组
        isArray(tclass, item, list, po, isSelf);
        ////2.2 泛型
        isGenerics(tclass, item, list, genType, po, isSelf);
        ////2.3 是基本类型 ：字符串，数字，文件，时间日期类型
        isStringOrNumberOrFile(tclass, item, list);
        ////2.4 是自定义对象类型
        isObject(tclass, item, list, po, isFirst, isSelf);

        //写入硬盘数据
        if (!po.isEmpty() && StringUtil.isNotEmpty(methodUUID)) {
            JsonUtil.writer(path, po);
        }
    }

    /**
     * 获得参数项对象
     * 没有设置dataType和pid属性，需要后边动态判断类型自动设置
     *
     * @param tclass    参数类型
     * @param paramName 参数名称
     * @param po        从硬盘读取的数据
     * @param key       参数的唯一标识 ： 参数名称-父参数名称
     * 参数名和父参数名可以唯一确认一个 参数，
     * 这样处理，方便利用hashcode算法，快速查询
     * @return ParamItem
     */
    private static String parentClassName = null;//父类

    private static ParamItem getParamItem(Class tclass, String paramName, JSONObject po, String key) {

        Map<String, String> fieldsNotes = null;
        //是自定义类型
        if (isMyClass(tclass) && tclass != Object.class) {
            parentClassName = tclass.getName();
        } else if (parentClassName != null) {
            fieldsNotes = StringUtil.getFieldsNotes(parentClassName);
        }
        String description = null;
        if (fieldsNotes != null) {
            description = StringUtil.isNotEmpty(fieldsNotes.get(paramName)) ? fieldsNotes.get(paramName) : paramName;
        }
        ParamItem item = new ParamItem();
        //设置父类名称
        if (parentClassName != null&&StringUtil.isNotEmpty(description)) {
            item.setParentClassName(parentClassName);
        }
        if (po.isEmpty() || po.getJSONObject(key) == null || po.getJSONObject(key).isEmpty()) {
            //1.设置名称
            item.setShow(true);
            item.setName(paramName);
            //2.设置默认值
            Object defaultValue = getObjectDefaultValue(tclass);
            item.setDefaultValue(defaultValue == null ? "null" : defaultValue);
            //3.设置描述
            item.setDescription(description);
            //4.写入硬盘
            VO vo = new VO().set("defaultVale", defaultValue == null ? "null" : defaultValue)
                    .set("description", description)
                    .set("required", true)
                    .set("show", true);
            po.put(key, vo);
        } else {
            //1.设置名称
            item.setName(paramName);
            //2.设置默认值
            item.setDefaultValue(po.getJSONObject(key).get("defaultVale"));
            //3.设置描述
            String des = po.getJSONObject(key).getString("description");
            item.setDescription(StringUtil.isNotEmpty(des) ? des : description);
            //4.设置是否必须
            item.setRequired(po.getJSONObject(key).getBoolean("required"));
            //5.设置是否显示
            item.setShow(po.getJSONObject(key).getBoolean("show"));
        }
        return item;
    }

    /**
     * 获得对象的默认值
     *
     * @param tclass class对象
     * @return Object
     */
    private static Object getObjectDefaultValue(Class tclass) {
        Object defaultValue = null;
        //数字类型 默认值 0
        if (Number.class.isAssignableFrom(tclass)) {
            defaultValue = 0;
        }
        //字符串类型 默认 ""
        else if (CharSequence.class.isAssignableFrom(tclass)) {
            defaultValue = "";
        }
//        //文件类型 默认"file"
//        else if (MultipartFile.class.isAssignableFrom(tclass)) {
//            defaultValue = "file";
//        }
        return defaultValue;
    }

    /**
     * 判断参数类型是否为泛型
     *
     * @param tclass  参数的Class
     * @param item    参数项
     * @param list    参数项列表
     * @param genType 参数的通用给类型
     * @param po
     */
    private static void isGenerics(Class tclass, ParamItem item, List<ParamItem> list, Type genType, JSONObject po, boolean isSelf) {
        //得到泛型
        if (genType instanceof ParameterizedType) {
            ParameterizedType aType = (ParameterizedType) genType;
            Type[] parameterArgTypes = aType.getActualTypeArguments();
            //todo 先支持collection和map类型 后期有需要再加
            if (Collection.class.isAssignableFrom(tclass)) {//是Collection
                Class typeClass = (Class) parameterArgTypes[0];
                item.setDataType(Const.array + typeClass.getSimpleName());
                list.add(item);
                isType(typeClass, null, item.getName(), list, null, null, po, false, isSelf, false);
            } else {
                Class typeClass = (Class) parameterArgTypes[0];
                item.setDataType(Const.object + typeClass.getSimpleName());
                list.add(item);
            }

//            if (Map.class.isAssignableFrom(tclass)) {//todo 是 Map map比较特殊，只能运行时得到值，后期处理
//                Class typeClass = (Class) parameterArgTypes[0];
//                item.setDataType(Const.object + typeClass.getSimpleName());
//                list.add(item);
//                isType(typeClass, null, item.getName(), list, null);
//            }
        }
    }

    /**
     * 判断参数类型是否是自定义类型
     *
     * @param tclass  参数的Class
     * @param item    参数项
     * @param list    参数项列表
     * @param po
     * @param isSelf  对象是否是自嵌套（自己引用自己）
     * @param isFirst
     */
    private static void isObject(Class tclass, ParamItem item, List<ParamItem> list, JSONObject po, boolean isFirst, boolean isSelf) {
        if (isMyClass(tclass)) {
            //读取硬盘的数据
            String path = Const.beanPath + tclass.getName() + ".json";
            JSONObject objectPo = JsonUtil.readerObject(path);
            String pid = null;
            //如果是返回值  舍弃第一个类型
            if (!isFirst || tclass == Object.class) {
                pid = item.getName();
                item.setDataType(Const.object + tclass.getSimpleName());//自定义对象类型为对象的名称
                list.add(item);
            }

            //获得对象的所有字段
            Field[] fields = tclass.getDeclaredFields();
            List<Field> fieldList = removeStaticAndFinal(fields);
            if (fieldList.size() > 0) {
                for (Field field : fieldList) {
                    Class<?> typeClass = field.getType();
                    String fieldName = field.getName();
                    //存入硬盘
                    Object defaultValue = getObjectDefaultValue(tclass);
                    VO vo = new VO().set("defaultVale", defaultValue == null ? "null" : defaultValue)
                            .set("description", fieldName)
                            .set("required", true);
                    objectPo.put(fieldName, vo);
                    JsonUtil.writer(path, objectPo);

                    //考虑对象的字段可能是对象  可能存在 自嵌套 互相嵌套的类
                    if (typeClass == tclass && isSelf) {//自嵌套  只走一次
                        isType(typeClass, fieldName, pid, list, null, null, po, false, false, false);//递归
                    }
                    if (typeClass != tclass) {
                        isType(typeClass, fieldName, pid, list, null, null, po, false, true, false);//递归
                    }

                }
            }
        }
    }

    /**
     * 去掉static的final修饰的字段
     *
     * @param fields 字段列表
     * @return Field[]
     */
    private static List<Field> removeStaticAndFinal(Field[] fields) {
        List<Field> fieldList = new ArrayList<>();
        if (fields.length > 0) {
            for (Field field : fields) {
                String modifier = Modifier.toString(field.getModifiers());
                if (modifier.contains("static") || modifier.contains("final")) {
                    //舍弃
                } else {
                    fieldList.add(field);
                }
            }
        }
        return fieldList;
    }

    /**
     * 是否是自定义类型
     *
     * @param clz class对象
     * @return boolean
     */
    private static boolean isMyClass(Class<?> clz) {
        if (clz == null) {
            return false;
        }
        //排除 spring的文件类型
        if (MultipartFile.class.isAssignableFrom(clz)) {
            return false;
        }
        //排除数组
        if (clz.isArray()) {
            return false;
        }
        //Object 类型特殊处理
        if (clz == Object.class) {
            return true;
        }
        //只能是jdk的根加载器
        return clz.getClassLoader() != null;
    }

    /**
     * 判断是否是数组类型
     *
     * @param tclass 参数的Class
     * @param item   参数项
     * @param list   参数项列表
     * @param po
     * @param isSelf
     */
    private static void isArray(Class tclass, ParamItem item, List<ParamItem> list, JSONObject po, boolean isSelf) {
        if (tclass.isArray()) {
            //获得数组类型
            Class typeClass = tclass.getComponentType();
            String shortName = typeClass.getSimpleName();
            item.setDataType(Const.array + shortName);
            //添加到list
            list.add(item);
            //处理多维数组维数组
            isType(typeClass, null, item.getName(), list, null, null, po, false, isSelf, false);
        }
    }


    /**
     * 判断是否是非对象类型： 字符串，数字，文件
     *
     * @param tclass 参数的Class
     * @param item   参数项
     * @param list   参数项列表
     */
    private static void isStringOrNumberOrFile(Class tclass, ParamItem item, List<ParamItem> list) {
        //数字
        if (Number.class.isAssignableFrom(tclass) || Const.number.equals(typeMap.get(tclass))) {
            item.setDataType(Const.number);
            list.add(item);
        }
        //字符串
        if (CharSequence.class.isAssignableFrom(tclass) || Character.class.isAssignableFrom(tclass) || Const.string.equals(typeMap.get(tclass))) {
            item.setDataType(Const.string);
            list.add(item);
        }
        //boolean
        if (Boolean.class.isAssignableFrom(tclass) || Const.booleann.equals(typeMap.get(tclass))) {
            item.setDataType(Const.booleann);
            list.add(item);
        }
        //文件 MultipartFile
        if (InputStreamSource.class.isAssignableFrom(tclass)) {
            item.setDataType(Const.file);
            list.add(item);
        }
        //文件 MultipartFile
        if (Date.class.isAssignableFrom(tclass)) {
            item.setDataType(Const.date);
            list.add(item);
        }
    }


    /**
     * 获得请求方式
     *
     * @param method 方法
     * @return String
     */
    private static String getType(Method method) {
        //获得参数类型
        //get: url ,path
        //post: from,json
        //put: json
        //delete: path
        String type = Const.JSON;
        String requestMethod = SpringUtil.getRequestMethod(method);
        switch (requestMethod) {
            case Const.GET:
            case Const.DELETE:
                if (containsPathVariableAnnotation(method.getParameterAnnotations())) {
                    type = Const.URI;
                } else {
                    type = Const.URL;
                }
                break;
            case Const.PUT:
            case Const.POST:
                if (containsRequestBodyAnnotation(method.getParameterAnnotations())) {
                    type = Const.JSON;
                } else {
                    type = Const.FROM;
                }
                break;
        }
        return type;
    }

    private static boolean containsRequestBodyAnnotation(Annotation[][] parameterAnnotations) {
        for (Annotation[] annotations : parameterAnnotations) {
            for (Annotation annotation : annotations) {
                if (annotation instanceof RequestBody) {
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean containsPathVariableAnnotation(Annotation[][] parameterAnnotations) {
        for (Annotation[] annotations : parameterAnnotations) {
            for (Annotation annotation : annotations) {
                if (annotation instanceof PathVariable) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 根据名称获得方法对象
     *
     * @param claszz 类对象
     * @return Method
     */
    public static Method getMethod(Class<?> claszz, String name) {
        Method[] methods = claszz.getMethods();
        for (Method m : methods) {
            if (name.equals(m.getName())) {
                return m;
            }
        }
        return null;
    }


    /**
     * 得到返回参数类型 组成的参数列表
     *
     * @param method 方法
     * @param methodUUID
     * @return Params
     */
    public static Params getReturn(Method method, String methodUUID) {
        //获得方法的返回值
        Class<?> rclass = method.getReturnType();

        //1.封装响应数据
        Params params = new Params();
        //2.设置请求或响应类型
        if (rclass.getTypeName().equals(void.class.getTypeName())) {
            params.setType(Const.BLOB);
        } else {
            params.setType(Const.JSON);
        }
        List<ParamItem> list = new ArrayList<>();
        isType(rclass, null, null, list, null, methodUUID, null, true, true,true);
        //3.设置参数列表
        params.setParams(list);
        return params;
    }

}

